package gr.uop.cst.context.adaptation.aspectj;

import gr.uop.cst.context.adaptation.artifacts.oom.AfterCallMethodAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.AfterExecuteMethodAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.AfterReadFieldAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.AfterWriteFieldAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.BeforeCallMethodAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.BeforeExecuteMethodAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.BeforeReadFieldAdaptation;
import gr.uop.cst.context.adaptation.artifacts.oom.BeforeWriteFieldAdaptation;
import gr.uop.cst.context.adaptation.ContextAdaptation;
import gr.uop.cst.context.adaptation.ContextAdaptationGenerator;
import java.lang.reflect.Member;

/**
 *
 * @author Ioannis Bourlakos <mi49@uop.gr>
 */
public class AspectJContextAdaptationGenerator implements ContextAdaptationGenerator {

    private StringBuffer adaptationCode;
    private int noOfAdaptations;
    private AspectJAdaptationCodePrinter codeOutput;
    private String rulesObjectFile;

    public AspectJContextAdaptationGenerator(String rulesObjectFile) {
        this.rulesObjectFile = rulesObjectFile;
        this.adaptationCode = new StringBuffer();
        this.noOfAdaptations = 0;
        try {
            this.codeOutput = new AspectJAdaptationCodePrinter();
        } catch (Exception ex) {
            throw new Error("Could not create file infrastructure for generator.");
        }
        this.intro();
    }

    private void intro() {
        preamble();
        fields();
        methods();
        mainExecutionPointcut();
        beforeMainExecutionAdvice();
        afterMainExecutionAdvice();
    }

    private void preamble() {
        p("import gr.uop.cst.context.base.*;").ln();
        p("import gr.uop.cst.context.adaptation.*;").ln();
        p("import gr.uop.cst.context.adaptation.aspectj.*;").ln();
        p("import java.io.*;");
        ln();
        p("public aspect CustomAdaptationAspect { ").ln();
    }

    private void fields() {
        t().p("/* FIELDS */").ln();
        t().p("public static final AspectJContextLookup LOOKUP = new AspectJContextLookup();").ln();
        t().p("public static final ContextRuleInputStream CIS; ").ln();
        t().p("static {").ln();
        t().t().p("try {").ln();
        t().t().t().p("CIS = new ContextRuleInputStream(\"" + rulesObjectFile + "\");").ln();
        t().t().p("}").p("catch(Exception ex) {").ln();
        t().t().t().p("throw new RuntimeException(ex);").ln();
        t().t().p("}").ln();
        t().p("}").ln();
        t().p("/* end of FIELDS */").ln();
        ln();
    }

    private void methods() {
        t().p("/* METHODS */").ln();
        ln();
        t().p("private static void loadContextRules() {").ln();
        t().t().p("while (true) {").ln();
        t().t().t().p("try {").ln();
        t().t().t().t().p("ContextRule rule = CIS.readContextRule();").ln();
        t().t().t().t().p("LOOKUP.register(rule);").ln();
        t().t().t().p("}").ln();
        t().t().t().p("catch(EOFException ignored) {").ln();
        t().t().t().t().p("break;").ln();
        t().t().t().p("}").ln();
        t().t().t().p("catch(IOException ex) {").ln();
        t().t().t().t().p("throw new RuntimeException(ex);").ln();
        t().t().t().p("}").ln();
        t().t().t().p("catch(ClassNotFoundException ex) {").ln();
        t().t().t().t().p("throw new RuntimeException(ex);").ln();
        t().t().t().p("}").ln();
        t().t().p("}").ln();
        t().p("}").ln();
        ln();
        t().p("/* end of METHODS */").ln();
        ln();
    }
    
    private void mainExecutionPointcut() {
        t().p("/* MAIN EXECUTION POINTCUT */").ln();
        t().p("pointcut mainExecution(String[] args) : ").ln();
        t().t().p("execution(* Main.main(String[])) && args(args);").ln();
        ln();
        t().p("/* end of MAIN EXECUTION POINTCUT */").ln();
        ln();
    }

    private void beforeMainExecutionAdvice() {
        t().p("/* BEFORE MAIN ADVICE */").ln();
        t().p("before(String[] args) : mainExecution(args) {").ln();
        t().t().p("// do things before main executes").ln();
        t().t().p("loadContextRules();").ln();
        t().p("}").ln();
        t().p("/* end of BEFORE MAIN ADVICE */").ln();
        ln();
    }

    private void afterMainExecutionAdvice() {
        t().p("/* AFTER MAIN ADVICE */").ln();
        t().p("after(String[] args) : mainExecution(args) {").ln();
        t().t().p("// do things after main executes").ln();
        t().t().p("ContextObject.deactivateAllInstances();").ln();
        t().p("}").ln();
        t().p("/* end of AFTER MAIN ADVICE */").ln();
        ln();
    }

    private void outro() {
        p("}").ln();
    }

    private AspectJContextAdaptationGenerator p(String s) {
        adaptationCode.append(s);
        return this;
    }

    private AspectJContextAdaptationGenerator ln() {
        adaptationCode.append(System.getProperties().getProperty("line.separator"));
        return this;
    }

    private AspectJContextAdaptationGenerator t() {
        adaptationCode.append("    ");
        return this;
    }

    public String saveCode() {
        String filename;
        codeOutput.write(getCode());
        codeOutput.close();
        filename = codeOutput.getFilename();
        return filename;
    }

    public String getCode() {
        outro();
        String result = adaptationCode.toString();
        return result;
    }

    public void visit(ContextAdaptation ca) {
        if (ca instanceof BeforeWriteFieldAdaptation) {
            visit((BeforeWriteFieldAdaptation) ca);
        } else if (ca instanceof AfterCallMethodAdaptation) {
            visit((AfterCallMethodAdaptation) ca);
        } else if (ca instanceof AfterExecuteMethodAdaptation) {
            visit((AfterExecuteMethodAdaptation) ca);
        } else if (ca instanceof AfterReadFieldAdaptation) {
            visit((AfterReadFieldAdaptation) ca);
        } else if (ca instanceof AfterWriteFieldAdaptation) {
            visit((AfterWriteFieldAdaptation) ca);
        } else if (ca instanceof BeforeCallMethodAdaptation) {
            visit((BeforeCallMethodAdaptation) ca);
        } else if (ca instanceof BeforeExecuteMethodAdaptation) {
            visit((BeforeExecuteMethodAdaptation) ca);
        } else if (ca instanceof BeforeReadFieldAdaptation) {
            visit((BeforeReadFieldAdaptation) ca);
        } else if (ca instanceof BeforeWriteFieldAdaptation) {
            visit((BeforeWriteFieldAdaptation) ca);
        } else {
            throw new UnsupportedOperationException("Unknown type of adaptation: " + ca.getClass().getCanonicalName());
        }
    }
    
    private void appendAdviceBody(String pointcutName) {
        t().t().p("ContextRule rule = LOOKUP.getRuleFor(\"" + pointcutName + "\");").ln();
        t().t().p("if (rule == null) throw new RuntimeException(\"Rule \" + rule + \" is not present.\");").ln();
        t().t().p("if (rule.isActive()) {").ln();
        t().t().t().p("Action a = LOOKUP.getActionFor(\"" + pointcutName + "\");").ln();
        t().t().t().p("if (a == null) throw new RuntimeException(\"Action for rule \"+rule+\"is not present.\");").ln();
        t().t().t().p("a.run();").ln();
        t().t().p("}").ln();
    }

    public void visit(BeforeWriteFieldAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchFieldException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("set(* " + className + "." + memberName + ") && !withincode(" + className + ".new(..));").ln();
        // advice
        t().p("before() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(BeforeReadFieldAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchFieldException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("get(* " + className + "." + memberName + ") && !withincode(" + className + ".new(..));").ln();
        // advice
        t().p("before() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(AfterWriteFieldAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchFieldException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("set(* " + className + "." + memberName + ") && !withincode(" + className + ".new(..));").ln();
        // advice
        t().p("after() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(AfterReadFieldAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchFieldException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("get(* " + className + "." + memberName + ") && !withincode(" + className + ".new(..));").ln();
        // advice
        t().p("after() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(AfterCallMethodAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchMethodException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("call(* " + className + "." + memberName + "(..)" + ");").ln();
        // advice
        t().p("after() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(AfterExecuteMethodAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchMethodException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("execution(* " + className + "." + memberName + "(..)" + ");").ln();
        // advice
        t().p("after() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(BeforeCallMethodAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchMethodException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        // pointcut
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("call(* " + className + "." + memberName + "(..)" + ");").ln();
        // advice
        t().p("before() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }

    public void visit(BeforeExecuteMethodAdaptation adapt) {
        this.noOfAdaptations++;
        Member m = null;
        try {
            m = adapt.getMember();
        } catch (ClassNotFoundException ex) {
            p("// " + ex.toString()).ln();
        } catch (NoSuchMethodException ex) {
            p("// " + ex.toString()).ln();
        }
        String className = m.getDeclaringClass().getCanonicalName();
        String memberName = m.getName();
        String pointcutName = AspectJContextLookup.generatePointcutName(adapt);
        t().p("pointcut " + pointcutName + "():").ln();
        t().t().p("execution(* " + className + "." + memberName + "(..)" + ");").ln();
        t().p("before() : " + pointcutName + "() { ").ln();
        appendAdviceBody(pointcutName);
        t().p("}");
        ln();
    }
}
